Android 控件架构与自定义控件(二)

时间不会辜负每一个平静努力的人!

欢迎来到周建的博客: 共同致力于技术分享与交流

六 .自定义View

在自定义View时,我们通常会去重写onDraw() 方法来绘制View的显示内容。如果该View还需要使用wrap_content属性,那么还必须重写onMeasure() 方法。另外,通过自定义attrs属性,还可以设置新的属性配置值。

在View中通常有以下一些比较重要的回调方法

  • onFinishInflate(): 从XML 加载组件后回调
  • onSizeChanged() : 组件大小改变时回调
  • onMeasure() : 回调该方法来进行测量
  • onLayout() : 回调该方法来确定显示的位置
  • onTouchEvent() : 监听到触摸事件时回调

当然,创建自定义View的时候,并不需要重写所有的方法,只需要重写特定条件的回调方法即可。

通常情况下,有三种方法来实现自定义控件

  • 对现有控件进行扩展
  • 通过组合来实现新的控件
  • 重写View来实现全新的控件

1 对现有控件进行拓展

这是一个非常重要的自定义View方法,它可以在原生控件的基础上进行拓展,增加新功能、修改显示的UI等。一般来说,我们可以在onDraw() 方法中对原生控件行为进行拓展。

以TextView为例,来看看如何使用拓展原生控件的方法创建新的控件。比如想让一个TextView的背景更加丰富,给其多绘制几层背景。

原生的TextView使用onDraw() 方法绘制要显示的文字。当继承了系统的TextView之后,如果不重写其onDraw() 方法,则不会修改TextView的任何效果。可以认为在自定义的TextView中调用TextView类的onDraw() 方法来绘制了显示的文字,代码如下所示:

1
2
3
4
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}

程序调用super.onDraw(canvas)方法来实现原生控件的功能,但是在调用super.onDraw(canvas)方法之前和之后,我们都可以实现自己的逻辑,分别在系统绘制文字前后,完成自己的操作。

1
2
3
4
5
6
7
@Override
protected void onDraw(Canvas canvas) {
//在回调父类方法前,实现自己的逻辑,对TextView来说即是在绘 //制文本内容前
super.onDraw(canvas);
//在回调父类方法后,实现自己的逻辑,对TextView来说即是在绘 //制文本内容后
}

实现自定义TextView

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<kotlindemo.zhoujian.com.customeview.MyTextView
android:layout_width="200dp"
android:layout_height="50dp"
android:gravity="center_horizontal"
android:text="Android"
android:textSize="30sp"/>
</LinearLayout>

MyTextView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package kotlindemo.zhoujian.com.customeview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
/**
* Created by zhoujian on 2017/6/13.
*/
public class MyTextView extends AppCompatTextView
{
private Paint mPaint1;
private Paint mPaint2;
public MyTextView(Context context) {
super(context);
initPaint();
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint()
{
mPaint1 = new Paint();
mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
mPaint1.setStyle(Paint.Style.FILL);
mPaint2 = new Paint();
mPaint2.setColor(Color.YELLOW);
mPaint2.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
//绘制外层矩形
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint1);
//绘制内层矩形
canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,mPaint2);
canvas.save();
//绘制文本前平移10像素
canvas.translate(10,0);
//父类完成的方法,即绘制文本
super.onDraw(canvas);
canvas.restore();
}
}

实现效果:

这里写图片描述

下面看一个稍微复杂的TextView ,利用LinearGradient Shader和Matrix 来实现一个动态的文字闪动的效果,要想实现这一效果,可以利用Android中的Paint对象的Shader渲染器。通过设置一个不断变化的LinearGradient,并使用带有该属性的Paint对象来绘制显示的文字。首先,在onSizedChanged() 方法中进行对象的初始化工作,并根据View的宽带设置一个inearGradient渐变渲染器,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mViewWidth == 0){
mViewWidth = getMeasuredWidth();
if(mViewWidth>0){
mPaint = getPaint();
mLinearGradient = new LinearGradient(
0,
0,
mViewWidth,
0,
new int[]{Color.BLUE,0XFFFFFFFF,Color.BLUE},
null,
Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
mMatrix = new Matrix();
}
}
}

其中最关键的就是使用getPint() 方法获取当前绘制TextView的Paint对象设置原生TextView的LinearGradient属性。最后在onDraw() 方法中,通过矩阵的方式来不断平移渐变效果,从而在绘制文字时,产生动态的闪动效果,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mMatrix!=null)
{
mTranslate += mViewWidth/5;
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mMatrix.setTranslate(mTranslate,0);
mLinearGradient.setLocalMatrix(mMatrix);
postInvalidateDelayed(100);
}
}

完整代码

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<kotlindemo.zhoujian.com.customeview.ShineTextView
android:layout_width="300dp"
android:layout_height="50dp"
android:text="My Android TextView"
android:textSize="30sp"/>
</LinearLayout>

ShineTextView.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package kotlindemo.zhoujian.com.customeview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.graphics.Matrix;
/**
* Created by zhoujian on 2017/6/13.
*/
public class ShineTextView extends AppCompatTextView
{
private int mViewWidth = 0;
private int mTranslate = 0;
private Matrix mMatrix;
private Paint mPaint;
private LinearGradient mLinearGradient;
public ShineTextView(Context context) {
super(context);
}
public ShineTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ShineTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mViewWidth == 0){
mViewWidth = getMeasuredWidth();
if(mViewWidth>0){
mPaint = getPaint();
mLinearGradient = new LinearGradient(
0,
0,
mViewWidth,
0,
new int[]{Color.BLUE,0XFFFFFFFF,Color.BLUE},
null,
Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
mMatrix = new Matrix();
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mMatrix!=null)
{
mTranslate += mViewWidth/5;
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mMatrix.setTranslate(mTranslate,0);
mLinearGradient.setLocalMatrix(mMatrix);
postInvalidateDelayed(100);
}
}
}

显示效果:

这里写图片描述

2 创建复合控件

这种方式通常需要继承一个合适的ViewGroup,再给它添加指定功能的控件,从而组合成新的复合控件。通过这种方式创建的控件,我们一般会给它指定一些可配的属性,让它具有更强的拓展性。下面就以一个CommentTitle为示例

(1) 自定义属性

在res资源目录的values目录下创建一个attrs.xml 的属性定义文件,并在文件中通过以下代码定义相应的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CommentTitle">
<attr name="title" format="string" />
<attr name="titleTextSize" format="dimension" />
<attr name="titleTextColor" format="color" />
<attr name="leftTextColor" format="color" />
<attr name="leftBackground" format="reference|color" />
<attr name="leftText" format="string" />
<attr name="rightTextColor" format="color" />
<attr name="rightBackground" format="reference|color" />
<attr name="rightText" format="string" />
</declare-styleable>
</resources>

我们在代码中通过 declare-styleable 标签声明了使用自定义属性,并通过name 属性来确定引用的名称。左后通过 attr 标签来声明具体的自定义属性,比如在这里定义看标题文字的字体、大小、颜色等,并通过format 属性来指定属性的类型。这里需要注意的是,有些属性可以是颜色属性,也可以是引用属性。比如按钮的背景,可以把它指定为具体颜色,也可以把它指定为一张图片,搜索一使用 “ | ”来分隔不同的属性。

在确定好属性后,就可以创建一个自定义控件-CommentTitle,并让它继承自ViewGroup,从而组合一些需要的控件。这里为了简单,我们继承自RelativeLayout 。在构造方法中,通过以下代码获取自定义属性

1
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CommentTitle);

系统提供了TypedArray 这样的数据结构来获取自定义属性集,后面引用的styleable的CommentTitle,就是我们在xml中通过declare-styleable name=”CommentTitle” 所指定的name 名。接下来通过TypedArray对象的getString()、getColor() 等方法,就可以获取这些定义的属性值,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 通过这个方法,将你在atts.xml中定义的declare-styleable
// 的所有属性的值存储到TypedArray中
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CommentTitle);
// 从TypedArray中取出对应的值来为要设置的属性赋值
mLeftTextColor = ta.getColor(
R.styleable.CommentTitle_leftTextColor, 0);
mLeftBackground = ta.getDrawable(
R.styleable.CommentTitle_leftBackground);
mLeftText = ta.getString(R.styleable.CommentTitle_leftText);
mRightTextColor = ta.getColor(
R.styleable.CommentTitle_rightTextColor, 0);
mRightBackground = ta.getDrawable(
R.styleable.CommentTitle_rightBackground);
mRightText = ta.getString(R.styleable.CommentTitle_rightText);
mTitleTextSize = ta.getDimension(
R.styleable.CommentTitle_titleTextSize, 10);
mTitleTextColor = ta.getColor(
R.styleable.CommentTitle_titleTextColor, 0);
mTitle = ta.getString(R.styleable.CommentTitle_title);
// 获取完TypedArray的值后,一般要调用
// recyle方法来避免重新创建的时候的错误
ta.recycle();

这里需要注意的是,当获取所有的属性值后,需要调用TypedArray的recycle()方法来完成资源的回收

(2) 组合控件

接下来,就可以开始组合控件了。CommentTitle实际上由三个控价组成,即左边的点击按钮 mLeftButton,右边的点击按钮 mRightButton和中间的标题mTitleView。通过动态添加控件的方式,使用addView() 方法将这三个控件加入到自定义的CommentTitle中,并给它们设置我们前面所获取的具体的属性值,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);
// 为创建的组件元素赋值
// 值就来源于我们在引用的xml文件中给对应属性的赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackground);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleTextColor);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setGravity(Gravity.CENTER);
// 为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
// 添加到ViewGroup
addView(mLeftButton, mLeftParams);
mRightParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
addView(mRightButton, mRightParams);
mTitlepParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
addView(mTitleView, mTitlepParams);

那么如何来给两个按钮设计点击事件呢? 这就用到接口回调的思想,将具体的实现逻辑交给调用者,实现过程如下

定义接口:

1
2
3
4
5
6
7
8
9
//定义接口,具体调用由调用者实现
public interface CommentTitleClickListener{
//左边按钮点击事件
void leftClick();
//右边按钮点击事件
void rightClick();
}

暴露接口给调用者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 按钮的点击事件,不需要具体的实现,
// 只需调用接口的方法,回调的时候,会有具体的实现
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
1
2
3
4
5
// 暴露一个方法给调用者来注册接口回调
// 通过接口来获得回调者对接口方法的实现
public void setOnCommentTitleClickListener(CommentTitleClickListener mListener) {
this.mListener = mListener;
}

为了能动态控制按钮的显示和隐藏,可以使用公共方法来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 设置按钮的显示与否 通过id区分按钮,flag区分是否显示
*
* @param id id
* @param flag 是否显示
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
}

最后给出完整的代码

CommentTitle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package kotlindemo.zhoujian.com.customeview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* Created by zhoujian on 2017/6/14.
*/
public class CommentTitle extends RelativeLayout {
// CommentTitle:左按钮、右按钮、标题
private Button mLeftButton;
private Button mRightButton;
private TextView mTitleView;
// 布局属性,用来控制组件元素在ViewGroup中的位置
private LayoutParams mLeftParams, mTitlepParams, mRightParams;
// 左按钮的属性值,即我们在atts.xml文件中定义的属性
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
// 右按钮的属性值,即我们在atts.xml文件中定义的属性
private int mRightTextColor;
private Drawable mRightBackground;
private String mRightText;
// 标题的属性值,即我们在atts.xml文件中定义的属性
private float mTitleTextSize;
private int mTitleTextColor;
private String mTitle;
// 映射传入的接口对象
private CommentTitleClickListener mListener;
public CommentTitle(Context context) {
super(context);
}
public CommentTitle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CommentTitle(Context context, AttributeSet attrs) {
super(context, attrs);
//设置CommentTitle的背景色
setBackgroundColor(0xFFF59563);
// 通过这个方法,将你在atts.xml中定义的declare-styleable
// 的所有属性的值存储到TypedArray中
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CommentTitle);
// 从TypedArray中取出对应的值来为要设置的属性赋值
mLeftTextColor = ta.getColor(
R.styleable.CommentTitle_leftTextColor, 0);
mLeftBackground = ta.getDrawable(
R.styleable.CommentTitle_leftBackground);
mLeftText = ta.getString(R.styleable.CommentTitle_leftText);
mRightTextColor = ta.getColor(
R.styleable.CommentTitle_rightTextColor, 0);
mRightBackground = ta.getDrawable(
R.styleable.CommentTitle_rightBackground);
mRightText = ta.getString(R.styleable.CommentTitle_rightText);
mTitleTextSize = ta.getDimension(
R.styleable.CommentTitle_titleTextSize, 10);
mTitleTextColor = ta.getColor(
R.styleable.CommentTitle_titleTextColor, 0);
mTitle = ta.getString(R.styleable.CommentTitle_title);
// 获取完TypedArray的值后,一般要调用
// recyle方法来避免重新创建的时候的错误
ta.recycle();
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);
// 为创建的组件元素赋值
// 值就来源于我们在引用的xml文件中给对应属性的赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackground);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleTextColor);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setGravity(Gravity.CENTER);
// 为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
// 添加到ViewGroup
addView(mLeftButton, mLeftParams);
mRightParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
addView(mRightButton, mRightParams);
mTitlepParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
addView(mTitleView, mTitlepParams);
// 按钮的点击事件,不需要具体的实现,
// 只需调用接口的方法,回调的时候,会有具体的实现
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
}
//定义接口,具体调用由调用者实现
public interface CommentTitleClickListener{
//左边按钮点击事件
void leftClick();
//右边按钮点击事件
void rightClick();
}
// 暴露一个方法给调用者来注册接口回调
// 通过接口来获得回调者对接口方法的实现
public void setOnCommentTitleClickListener(CommentTitleClickListener mListener) {
this.mListener = mListener;
}
/**
* 设置按钮的显示与否 通过id区分按钮,flag区分是否显示
*
* @param id id
* @param flag 是否显示
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
}
}

在布局文件中,引入命名空间

1
2
//custom:可以随便定义
xmlns:custom="http://schemas.android.com/apk/res-auto"

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- custom:leftBackground="@drawable/blue_button"
custom:rightBackground="@drawable/blue_button"-->
<kotlindemo.zhoujian.com.customeview.CommentTitle
android:id="@+id/commentTitle"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftText="返回"
custom:leftTextColor="#FFFFFF"
custom:rightText="更多"
custom:rightTextColor="#FFFFFF"
custom:title="自定义标题"
custom:titleTextColor="#FFFFFF"
custom:titleTextSize="5sp"/>
</RelativeLayout>

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package kotlindemo.zhoujian.com.customeview;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends Activity {
private CommentTitle mCommentTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获得我们创建的topbar
mCommentTitle = (CommentTitle) findViewById(R.id.commentTitle);
// 为topbar注册监听事件,传入定义的接口
// 并以匿名类的方式实现接口内的方法
mCommentTitle.setOnCommentTitleClickListener(
new CommentTitle.CommentTitleClickListener() {
@Override
public void rightClick() {
Toast.makeText(MainActivity.this,
"right", Toast.LENGTH_SHORT)
.show();
}
@Override
public void leftClick() {
Toast.makeText(MainActivity.this,
"left", Toast.LENGTH_SHORT)
.show();
}
});
// 控制topbar上组件的状态
mCommentTitle.setButtonVisable(0, true);
mCommentTitle.setButtonVisable(0, true);
}
}

CommentTitle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package kotlindemo.zhoujian.com.customeview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* Created by zhoujian on 2017/6/14.
*/
public class CommentTitle extends RelativeLayout {
// CommentTitle:左按钮、右按钮、标题
private Button mLeftButton;
private Button mRightButton;
private TextView mTitleView;
// 布局属性,用来控制组件元素在ViewGroup中的位置
private LayoutParams mLeftParams, mTitlepParams, mRightParams;
// 左按钮的属性值,即我们在atts.xml文件中定义的属性
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
// 右按钮的属性值,即我们在atts.xml文件中定义的属性
private int mRightTextColor;
private Drawable mRightBackground;
private String mRightText;
// 标题的属性值,即我们在atts.xml文件中定义的属性
private float mTitleTextSize;
private int mTitleTextColor;
private String mTitle;
// 映射传入的接口对象
private CommentTitleClickListener mListener;
public CommentTitle(Context context) {
super(context);
}
public CommentTitle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CommentTitle(Context context, AttributeSet attrs) {
super(context, attrs);
//设置CommentTitle的背景色
setBackgroundColor(Color.parseColor("#3EC5FF"));
// 通过这个方法,将你在atts.xml中定义的declare-styleable
// 的所有属性的值存储到TypedArray中
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.CommentTitle);
// 从TypedArray中取出对应的值来为要设置的属性赋值
mLeftTextColor = ta.getColor(
R.styleable.CommentTitle_leftTextColor, 0);
mLeftBackground = ta.getDrawable(
R.styleable.CommentTitle_leftBackground);
mLeftText = ta.getString(R.styleable.CommentTitle_leftText);
mRightTextColor = ta.getColor(
R.styleable.CommentTitle_rightTextColor, 0);
mRightBackground = ta.getDrawable(
R.styleable.CommentTitle_rightBackground);
mRightText = ta.getString(R.styleable.CommentTitle_rightText);
mTitleTextSize = ta.getDimension(
R.styleable.CommentTitle_titleTextSize, 10);
mTitleTextColor = ta.getColor(
R.styleable.CommentTitle_titleTextColor, 0);
mTitle = ta.getString(R.styleable.CommentTitle_title);
// 获取完TypedArray的值后,一般要调用
// recyle方法来避免重新创建的时候的错误
ta.recycle();
mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context);
// 为创建的组件元素赋值
// 值就来源于我们在引用的xml文件中给对应属性的赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText);
mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackground);
mRightButton.setText(mRightText);
mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleTextColor);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setGravity(Gravity.CENTER);
// 为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
// 添加到ViewGroup
addView(mLeftButton, mLeftParams);
mRightParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
addView(mRightButton, mRightParams);
mTitlepParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
addView(mTitleView, mTitlepParams);
// 按钮的点击事件,不需要具体的实现,
// 只需调用接口的方法,回调的时候,会有具体的实现
mRightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.rightClick();
}
});
mLeftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.leftClick();
}
});
}
//定义接口,具体调用由调用者实现
public interface CommentTitleClickListener{
//左边按钮点击事件
void leftClick();
//右边按钮点击事件
void rightClick();
}
// 暴露一个方法给调用者来注册接口回调
// 通过接口来获得回调者对接口方法的实现
public void setOnCommentTitleClickListener(CommentTitleClickListener mListener) {
this.mListener = mListener;
}
/**
* 设置按钮的显示与否 通过id区分按钮,flag区分是否显示
*
* @param id id
* @param flag 是否显示
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
}
}

显示效果:

这里写图片描述

Android 控件架构与自定义控件(一)

Android 控件架构与自定义控件(二)

Android 控件架构与自定义控件(三)

Android 控件架构与自定义控件(四)